// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.cpu;

import com.dustedpixels.common.Threads;
import com.dustedpixels.jasmin.common.Timeline;
import com.dustedpixels.jasmin.memory.Memory;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class CrewSharedMemory {
  private final Timeline rwTimeline;
  
  private final RwMemory rwMemory;
  private final RoMemory[] roMemories;
  
  private final int bufferSize;
  private final BufferItem[] buffer;
  
  private final Object mutex;
  private final Object rwMutex;
  private final Object roMutex;
  public int readersWaiting;

  private final int readersCount;
  
  public CrewSharedMemory(
      Timeline rwTimeline, 
      Timeline[] roTimelines,
      Memory rwMemory, 
      Memory[] roMemories, 
      int bufferSize) {
    
    this.rwTimeline = rwTimeline;
    
    this.readersCount = roTimelines.length;
    this.bufferSize = bufferSize;
    this.buffer = new BufferItem[bufferSize];
    for (int i = 0; i < bufferSize; i++) {
      this.buffer[i] = new BufferItem();
    }
    
    this.mutex = new Object(){};
    this.rwMutex = new Object(){};
    this.roMutex = new Object(){};
    this.readersWaiting = 0;

    this.rwMemory = new RwMemory(rwMemory, rwTimeline);
    this.roMemories = new RoMemory[bufferSize];
    for (int i = 0; i < roMemories.length; i++) {
      this.roMemories[i] = new RoMemory(roMemories[i], roTimelines[i]);
    }
  }
  
  public Memory getRwMemory() {
    return rwMemory;
  }
  
  public Memory[] getRoMemories() {
    return roMemories;
  }
   
  private final class RwMemory implements Memory {
    private final Timeline timeline;
    private final Memory memory;
    private int bufferIndex;
    
    public RwMemory(Memory memory, Timeline timeline) {
      this.timeline = timeline;
      this.memory = memory;
      this.bufferIndex = 0;
    }
    
    public byte read(int address) {
      return memory.read(address);
    }

    public void write(int address, byte value) {
      memory.write(address, value);
      
      BufferItem bufferItem = buffer[bufferIndex++];
      bufferItem.time = timeline.cycles();
      bufferItem.address = address;
      bufferItem.value = value;
      
      if (bufferIndex == bufferSize) {
        synchronized (mutex) {
          readersWaiting = 0;
        }
        
        synchronized (roMutex) {
          Threads.notifyAllOn(roMutex);
        }
        
        System.out.println("Writer waiting");
        synchronized (rwMutex) {
          Threads.waitOn(rwMutex);
        }
        System.out.println("Writer resumed");
      }
    }
  }
  
  private final class RoMemory implements Memory {
    private final Memory memory;
    private final Timeline timeline;
    private int rwCycles;
    private int bufferIndex;
    
    public RoMemory(Memory memory, Timeline timeline) {
      this.memory = memory;
      this.timeline = timeline;
      this.rwCycles = 0;
      this.bufferIndex = 0;
    }

    public byte read(int address) {
      long cycles = timeline.cycles();
      
      if (cycles >= rwCycles) {
        boolean startWriting;
        
        synchronized (mutex) {
          readersWaiting++;
          startWriting = (readersWaiting == readersCount);
        }
        
        if (startWriting) {
          synchronized (rwMutex) {
            Threads.notifyOn(rwMutex);
          }
        }
        
        System.out.println("Reader waiting");
        synchronized (roMutex) {
          Threads.waitOn(roMutex);
        }
        System.out.println("Reader resumed");
      } else {
        while (bufferIndex < bufferSize) {
          BufferItem bufferItem = buffer[bufferIndex];
          if (cycles >= bufferItem.time) {
            memory.write(bufferItem.address, bufferItem.value);
            bufferIndex++;
          } else {
            break;
          }
        }
      }
      
      return memory.read(address);
    }

    public void write(int address, byte value) {
      throw new IllegalStateException();
    }
  }
  
  private final class BufferItem {
    long time;
    int address;
    byte value;
  }
  
  public static void main(String[] args) {
    Timeline rwTimeline = new TimelineImpl();
    Timeline roTimeline = new TimelineImpl();
    
    Memory rwMemory = new MemoryImpl(4);
    Memory roMemory = new MemoryImpl(4);
    for (int address = 0; address < 0x10; address++) {
      rwMemory.write(address, (byte) address);
      roMemory.write(address, (byte) address);
    }
    
    CrewSharedMemory sharedMemory = new CrewSharedMemory(
        rwTimeline,
        new Timeline[] {roTimeline},
        rwMemory,
        new Memory[] {rwMemory},
        4);
    
    Thread rwThread = new Thread(new MemoryWriter(rwTimeline, sharedMemory.getRwMemory()));
    rwThread.start();
    
    Thread roThread = new Thread(new MemoryReader(roTimeline, sharedMemory.getRoMemories()[0]));
    roThread.start();
  }
}